package com.ody.util.code.generator;

import com.odianyun.util.date.DateUtils;
import com.odianyun.util.value.ValueUtils;
import com.ody.util.code.PlaceHolderHelper;
import java.io.File;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.odianyun.util.ResourceUtils;
import com.ody.util.code.CodeContext;
import com.ody.util.code.FreemarkerHelper;
import com.ody.util.code.model.IModelProvider;
import com.ody.util.code.model.Model;
import com.ody.util.code.model.POBean;
import org.springframework.util.StringUtils;

public abstract class AbstractCodeGenerator implements ICodeGenerator {
	protected Logger logger = LoggerFactory.getLogger(this.getClass());
	
	private String name;
	private String codeTemplate;
	private String[] ignoreModels;
	private boolean requireModel = true;
    private boolean skipOnEmpty = true;
	private String skip;
	private boolean once;
    private boolean globalOnce;
	private boolean hidden;
	
	private IModelProvider modelProvider;
	private int runTotal;

	@Override
	public void generate() throws Exception {
        if (globalOnce && ++ runTotal  > 1) {
            return;
        }
        
		Assert.notNull(codeTemplate, "Parameter codeTemplate cannot be null");
		Assert.notNull(modelProvider, "Parameter modelProvider cannot be null");
		
		List<CodeContext> ctxes = getRepeatCodeContextes(modelProvider);
		
		for (CodeContext ctx : ctxes) {
			beforeGenerate(ctx);
            
            if (isSkip(ctx)) {
                continue;
            }
			
			String codeTmpl = ctx.replace(codeTemplate);
			File tmplFile = new File(ResourceUtils.getResourcePath(codeTmpl));
			Assert.isTrue(tmplFile.exists(), "Code template ["+codeTmpl+"] is not found");
			
			Map<String, Object> models = convertToModel(ctx, this.ignoreModels);
			logger.trace(name+ "-Models:" + models);
			
			Boolean hasModel = (Boolean) models.get("hasModel");
			logger.trace(name + "-hasModel:" + hasModel);
			
			if (! requireModel || hasModel.booleanValue()) {
                if (tmplFile.isDirectory()) {
                    for (File subFile : tmplFile.listFiles()) {
                        String content = FreemarkerHelper.getHtml(subFile.getParent(), subFile.getName(), models);
                        doGenerate(content, ctx, subFile);
                    }
                } else {
                    String content = FreemarkerHelper.getHtml(tmplFile.getParent(), tmplFile.getName(), models);
                    doGenerate(content, ctx, tmplFile);
                }
			}
			
			afterGenerate(ctx);
            
            if (once) {
                break;
            }
		}
	}
	
	protected abstract void doGenerate(String content, CodeContext ctx, File tmplFile) throws Exception;
	
	private Map<String, Object> convertToModel(CodeContext ctx, String[] ignoreModels) {
		Map<String, Object> map = ctx.getCtxMap();
		Map<String, Object> model = Maps.newHashMapWithExpectedSize(map.size());
		int modelCount = 0;
		for (Entry<String, Object> entry : map.entrySet()) {
			Object value = entry.getValue();
			if (value instanceof String) {
				value = ctx.replace((String) value);
			}
				if (value instanceof POBean) {
					POBean po = (POBean) value;
					if (ignoreModels != null) {
						boolean ignore = false;
						for (String ignoreModel : ignoreModels) {
							if (po.isIgnore(ignoreModel)) {
								ignore = true;
								break;
							}
						}
						if (ignore) {
							continue;
						}
					}
					
					modelCount ++;
				}
			model.put(entry.getKey(), value);
		}
		if (modelCount > 0) {
			model.put("hasModel", Boolean.TRUE);
		} else {
			model.put("hasModel", Boolean.FALSE);
		}
		return model;
	}
	
	protected List<CodeContext> getRepeatCodeContextes(IModelProvider provider) {
		List<CodeContext> ctxList = Lists.newArrayList();
		
		CodeContext ctx = new CodeContext();
		ctxList.add(ctx);
		
        Date now = new Date();
        ctx.set("date", DateUtils.YYYY_MM_DD.format(now));
        ctx.set("dateTime", DateUtils.YYYY_MM_DD_HH_MI_SS.format(now));
        ctx.set("time", DateUtils.date2Str(now, "hh:mm:ss"));
		
		List<Model> repeatableModels = Lists.newArrayList();
		
		List<Model> modelList = provider.provide();
		
		for (Model model : modelList) {
			if (model.isRepeatable()) {
				repeatableModels.add(model);
				ctx.set("$$"+model.getName(), model.getValue());
			} else {
				ctx.set(model.getName(), model.getValue());
			}
		}
		
		if (! repeatableModels.isEmpty()) {
			for (Model model : repeatableModels) {
				ctxList = repeateModel(ctxList, model);
			}
		}
		
		return ctxList;
	}

	@SuppressWarnings("rawtypes")
	private List<CodeContext> repeateModel(List<CodeContext> ctxList, Model model) {
		List<CodeContext> rslt = Lists.newArrayList();
		Object values = model.getValue();
		
		if (values instanceof Collection) {
			Collection coll = (Collection) values;
			Assert.notEmpty(coll, "没有找到任何Model, 请检查配置");
			
			for (Object obj : coll) {
				for (CodeContext ctx : ctxList) {
					rslt.add(ctx.clone().set(model.getName(), obj));
				}
			}
		} else if (values.getClass().isArray()) {
			Assert.notEmpty((Object[]) values, "没有找到任何Model, 请检查配置");
			
			for (int i=0; i<Array.getLength(values); i++) {
				Object obj = Array.get(values, i);

				for (CodeContext ctx : ctxList) {
					rslt.add(ctx.clone().set(model.getName(), obj));
				}
			}
		} else {
			throw new RuntimeException("Model["+values+"] is not repeatable, expect Collection or Array");
		}
		
		return rslt;
	}
	
	protected void beforeGenerate(CodeContext ctx) {}
	
	protected void afterGenerate(CodeContext ctx) {}
	
	protected String getFileEncoding() {
		return "UTF-8";
	}

	public void setName(String name) {
		this.name = name;
	}
	public String getName() {
		return name;
	}
	public void setModelProvider(IModelProvider modelProvider) {
		this.modelProvider = modelProvider;
	}
	public void setCodeTemplate(String codeTemplate) {
		this.codeTemplate = codeTemplate;
	}
	public void setIgnoreModels(String[] ignoreModels) {
		this.ignoreModels = ignoreModels;
	}
	public void setRequireModel(boolean requireModel) {
		this.requireModel = requireModel;
	}
    public void setSkipOnEmpty(boolean skipOnEmpty) {
        this.skipOnEmpty = skipOnEmpty;
    }
    public void setSkip(String skip) {
        this.skip = skip;
    }
    
    public boolean isOnce() {
        return once;
    }
    
    public void setOnce(boolean once) {
        this.once = once;
    }
    
    public boolean isHidden() {
        return hidden;
    }
    
    public void setHidden(boolean hidden) {
        this.hidden = hidden;
    }
    
    public boolean getGlobalOnce() {
        return globalOnce;
    }
    
    public void setGlobalOnce(boolean globalOnce) {
        this.globalOnce = globalOnce;
    }
    
    protected boolean isSkip(CodeContext ctx) {
        if (StringUtils.hasText(this.skip)) {
            return ValueUtils.convert(PlaceHolderHelper.replace(skip, ctx.getCtxMap()), boolean.class);
        }
        return false;
    }
    
}
